/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Can read a line from character streams. Uses only a single character
 * "buffer".
 * <p>
 * A fast and efficient replacement for Buffered reader.
 * </p>
 */
public class LineReader extends InputStreamReader {
	private char buf;
	private boolean readAhead = false;

	/**
	 * Creates a LineReader instance out of an InputStream
	 * 
	 * @param in
	 *            InputStream from wich we read
	 */
	public LineReader(InputStream in) {
		super(in);
	}

	/**
	 * This method does nothing, do not use it.
	 */
	public void mark(int readLimit) {}

	/**
	 * We do not support mark(), so it returns constant false.
	 */
	public boolean markSupported() {
		return false;
	}

	/**
	 * Reads one character from the stream. Throws an exception when the
	 * underlying InputStreamReader.read() does.
	 * 
	 * @throws IOException
	 */
	public int read() throws IOException {
		if (readAhead) {
			readAhead = false;
			return buf;
		}
		return super.read();
	}

	/**
	 * Reads a character array from the stream. Throws an exception when the
	 * underlying InputStreamReader.read() does.
	 * 
	 * @param cbuf
	 *            Array of characters to fill.
	 * @throws IOException
	 */
	public int read(char[] cbuf) throws IOException {
		int ret = 0;
		if (readAhead) {
			cbuf[0] = buf;
			ret++;
			readAhead = false;
		}
		return super.read(cbuf, ret, cbuf.length - ret) + 1;
	}

	/**
	 * Reads a character array from the stream. Throws an exception when the
	 * underlying InputStreamReader.read() does.
	 * 
	 * @param cbuf
	 *            Array of characters to fill (partially?).
	 * @param off
	 *            Offset: first n characters in cbuf to be left alone
	 * @param len
	 *            Read that many characters
	 * @throws IOException
	 */
	public int read(char[] cbuf, int off, int len) throws IOException {
		if (readAhead) {
			cbuf[off] = buf;
			off++;
			readAhead = false;
		}
		return super.read(cbuf, off, len) + 1;
	}

	/**
	 * OK, this is the point. This method reads a line from the underlying
	 * InputStream. It recognizes Dos, Max and Unix line endings automatically
	 * (thats why we need a one character "buffer").
	 */
	public String readLine() throws IOException {
		StringBuffer sb = new StringBuffer();
		int c;
		while ((c = read()) > 0 && c != '\n' && c != '\r' && c != -1) {
			sb.append((char) c);
		}
		// By now, buf is empty.
		if (c == '\r') {
			// Dos, or Mac line ending?
			c = super.read();
			if (c != '\n' && c != -1) {
				// Push it back into the 'buffer'
				buf = (char) c;
				readAhead = true;
			}
		}
		return sb.toString();
	}

	/**
	 * Returns true if there is at least one character that can be returned
	 * immediately by a read() operation (without blocking the thread).
	 * 
	 * @throws IOException
	 */
	public boolean ready() throws IOException {
		if (readAhead) {
			return true;
		}
		return super.ready();
	}

	/**
	 * Again: mark not supported, so this should be used. If you do so, this
	 * method will blow an IOException into your face.
	 * 
	 * @throws IOException
	 */
	public void reset() throws IOException {
		throw new IOException("Mark is not supported");
	}

	/**
	 * Read n characters from the stream an discard them.
	 * 
	 * @param n
	 *            Number of characters to discard
	 * @throws IOException
	 */
	public long skip(long n) throws IOException {
		long ret = 0;
		if (readAhead) {
			readAhead = false;
			n--;
			ret++;
		}
		if (n > 0) {
			ret += super.skip(n);
		}
		return ret;
	}
}
